/* Certificate creation for TPM EK.
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/pem.h>
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/x509v3.h>
#include <trousers/tss.h>
#include <trousers/trousers.h>
#ifndef OPENSSL_NO_ENGINE
#include <openssl/engine.h>
#endif

#define TPMPASSWD "xenroot"

int mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int days);
int add_ext(X509 *cert, int nid, char *value);
EVP_PKEY* read_rsa_privkey();
EVP_PKEY* read_rsa_pubkey();
int verify_cert(X509*  x509);


/* Read the TPM EK
 * TODO : generate one and insert if does not exist
 * 
 */

EVP_PKEY* get_tpm_ek()
{
    
    TSS_HCONTEXT	hContext;
	TSS_HTPM    	hTPM;
    TSS_RESULT	    result;
	TSS_HKEY	    hPubek;
	UINT32		    modulusLen;
	UINT32		    e_size;
	BYTE		    *modulus;
    BYTE            *e;
    EVP_PKEY        *pk;
	RSA			    *ekRsa;
    TSS_HPOLICY     ekPolicy;


    result = Tspi_Context_Create(&hContext);
	if (result != TSS_SUCCESS) {
		printf ("Error 0x%x on Tspi_Context_Create\n", result);
		exit(result);
	}
	result = Tspi_Context_Connect(hContext, NULL);
	if (result != TSS_SUCCESS) {
		printf ("Error 0x%x on Tspi_Context_Connect\n", result);
		exit(result);
	}
	result = Tspi_Context_GetTpmObject (hContext, &hTPM);
	if (result != TSS_SUCCESS) {
		printf ("Error 0x%x on Tspi_Context_GetTpmObject\n", result);
		exit(result);
	}

	result = Tspi_TPM_GetPubEndorsementKey (hTPM, FALSE, NULL, &hPubek);

   	if (result == TCPA_E_DISABLED_CMD) {
		
		printf("\n Public PubEk access blocked, owner password required");

        result = Tspi_GetPolicyObject (hTPM, TSS_POLICY_USAGE, &ekPolicy);
        	if (result != TSS_SUCCESS) {
		    printf ("Error 0x%x on Tspi_Context_GetTpmObject\n", result);
		    exit(result);
	        }

        result = Tspi_Policy_SetSecret(ekPolicy, TSS_SECRET_MODE_PLAIN,
			    (UINT32)strlen(TPMPASSWD),(BYTE*)TPMPASSWD);

        if (result != TSS_SUCCESS) {
		    printf ("Error 0x%x on Tspi_Context_GetTpmObject\n", result);
		    exit(result);
	        } 
        result = Tspi_TPM_GetPubEndorsementKey (hTPM, TRUE, NULL, &hPubek);
        
	}
	if (result != TSS_SUCCESS)
		return NULL;
 

    result = Tspi_GetAttribData (hPubek, TSS_TSPATTRIB_RSAKEY_INFO,
		TSS_TSPATTRIB_KEYINFO_RSA_MODULUS, &modulusLen, &modulus);
	
	if (result != TSS_SUCCESS){
		printf ("Error 0x%x on Tspi_Context_GetAttr Modulus\n", result);
		return NULL;
		}
	
    if (modulusLen != 256) {
		Tspi_Context_FreeMemory (hContext, modulus);
		return NULL;
	}

    result = Tspi_GetAttribData(hPubek, TSS_TSPATTRIB_RSAKEY_INFO,
        TSS_TSPATTRIB_KEYINFO_RSA_EXPONENT, &e_size, &e);
    
    if (result != TSS_SUCCESS){
		printf ("Error 0x%x on Tspi_Context_GetAttr Exponent\n", result);
		Tspi_Context_FreeMemory (hContext, modulus);
        return NULL;
    }
    
    Tspi_Context_CloseObject (hContext, hPubek);
    ekRsa = RSA_new();
	ekRsa->n = BN_bin2bn (modulus, 256, NULL);
	ekRsa->e = BN_new();
    ekRsa->e = BN_bin2bn(e, e_size, NULL);

    if ((pk=EVP_PKEY_new()) == NULL){
        RSA_free(ekRsa);
        fprintf(stdout,"%s","Error creating a EK EVP");
        return NULL;
        }
	if (!EVP_PKEY_assign_RSA(pk,ekRsa)){
		EVP_PKEY_free(pk);
        RSA_free(ekRsa);
        fprintf(stdout,"%s","Error inserting ek in EVP");
        return NULL;
        }
    Tspi_Context_FreeMemory (hContext, modulus);
    Tspi_Context_FreeMemory (hContext, e);

    return pk;
}

int main(int argc, char **argv)
{
	BIO *bio_err;
	X509 *x509=NULL;
	EVP_PKEY *pkey=NULL;
    FILE * fp = NULL;
    
    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings(); 
	CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);
	bio_err=BIO_new_fp(stderr, BIO_NOCLOSE);
    pkey = read_rsa_privkey();
	
    mkcert(&x509,&pkey,2048,0,365);
    verify_cert(x509);
    
    fp = fopen("x509.cert","wb");
	PEM_write_PrivateKey(stdout,pkey,NULL,NULL,0,NULL, NULL);
	PEM_write_X509(stdout,x509);
    
    if(fp != NULL)
        i2d_X509_fp(fp,x509);
    fclose(fp);
    X509_free(x509);
	EVP_PKEY_free(pkey);
#ifndef OPENSSL_NO_ENGINE
	ENGINE_cleanup();
#endif
	CRYPTO_cleanup_all_ex_data();
	CRYPTO_mem_leaks(bio_err);
	BIO_free(bio_err);
	return(0);
}

int mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int days)
{
	X509 *x;
	EVP_PKEY *ek_pk;
	EVP_PKEY *pk;
	RSA *rsa;
	X509_NAME *name=NULL;
	if ((pkeyp == NULL) || (*pkeyp == NULL))
		{
		if ((pk=EVP_PKEY_new()) == NULL)
			{
			abort(); 
			return(0);
			}
		}
	else
		pk= *pkeyp;

	if ((x509p == NULL) || (*x509p == NULL)){
		if ((x=X509_new()) == NULL)
			goto err;
	}
	else 
		x= *x509p;

    rsa=NULL;
    ek_pk = get_tpm_ek();
    if (!ek_pk) {
        abort();
        fprintf(stdout,"%s","Error Reading public key from file");
    }


	X509_set_version(x,2);
	ASN1_INTEGER_set(X509_get_serialNumber(x),serial);
	X509_gmtime_adj(X509_get_notBefore(x),0);
	X509_gmtime_adj(X509_get_notAfter(x),(long)60*60*24*days);
	X509_set_pubkey(x,ek_pk);

	name=X509_get_subject_name(x);

	/* This function creates and adds the entry, working out the
	 * correct string type and performing checks on its length.
	 * Normally we'd check the return value for errors...
	 */
	X509_NAME_add_entry_by_txt(name,"C",
				MBSTRING_ASC, "US", -1, -1, 0);
	X509_NAME_add_entry_by_txt(name,"CN",
				MBSTRING_ASC, "Citrix", -1, -1, 0);

	/* Its self signed so set the issuer name to be the same as the
 	 * subject.
	 */
	X509_set_issuer_name(x,name);

	/* Add various extensions: standard extensions */
	add_ext(x, NID_basic_constraints, "critical,CA:TRUE");
	add_ext(x, NID_key_usage, "critical,keyCertSign,cRLSign");

	add_ext(x, NID_subject_key_identifier, "hash");

	/* Some Netscape specific extensions */
	add_ext(x, NID_netscape_cert_type, "sslCA");

	add_ext(x, NID_netscape_comment, "TPM Cert for Xenserver");


#ifdef CUSTOM_EXT
	/* Maybe even add our own extension based on existing */
	{
		int nid;
		nid = OBJ_create("1.2.3.4", "MyAlias", "My Test Alias Extension");
		X509V3_EXT_add_alias(nid, NID_netscape_comment);
		add_ext(x, nid, "example comment alias");
	}
#endif
	
	if (!X509_sign(x,pk,EVP_sha1()))
		goto err;

	*x509p=x;
	EVP_PKEY_free(ek_pk);
    return(1);
err:
	return(0);
}

/* Add extension using V3 code: we can set the config file as NULL
 * because we wont reference any other sections.
 */

int add_ext(X509 *cert, int nid, char *value)
{
	X509_EXTENSION *ex;
	X509V3_CTX ctx;
	/* This sets the 'context' of the extensions. */
	/* No configuration database */
	X509V3_set_ctx_nodb(&ctx);
	/* Issuer and subject certs: both the target since it is self signed,
	 * no request and no CRL
	 */
	X509V3_set_ctx(&ctx, cert, cert, NULL, NULL, 0);
	ex = X509V3_EXT_conf_nid(NULL, &ctx, nid, value);
	if (!ex)
		return 0;

	X509_add_ext(cert,ex,-1);
	X509_EXTENSION_free(ex);
	return 1;
}

EVP_PKEY* read_rsa_privkey()
{

    FILE *fp = NULL;
    EVP_PKEY *pk;
    RSA *rsa;
    
    fp = fopen("ssh_host_rsa_key","r");
    if(!fp) {
        fprintf(stdout,"%s","Error Reading key file");
        return NULL;
    }
    rsa = RSA_new();
    
    if( !PEM_read_RSAPrivateKey(fp, &rsa, NULL,NULL)) {
        RSA_free(rsa);
        fclose(fp);
        fprintf(stdout,"%s","Error Reading private Key");
        return NULL;
        }
    fclose(fp);
    if ((pk=EVP_PKEY_new()) == NULL){
        RSA_free(rsa);
        fprintf(stdout,"%s","Error creating a new EVP");
        return NULL;
        }
 
	if (!EVP_PKEY_assign_RSA(pk,rsa)){
		EVP_PKEY_free(pk);
        RSA_free(rsa);
        fprintf(stdout,"%s","Error inserting key in EVP");
        return NULL;
        }
    return pk;
}

/*Read public key for xen
 * this key need to generated from privae key
 * using openssl commands
 *
 * */

EVP_PKEY* read_rsa_pubkey()
{

    FILE *fp = NULL;
    EVP_PKEY *pk;
    RSA *rsa;
    
    fp = fopen("out.pub","r");
    if(!fp) {
        fprintf(stdout,"%s","Error Reading key file");
        return NULL;
    }

    rsa = RSA_new();
    
    if( !PEM_read_RSA_PUBKEY(fp, &rsa, NULL,NULL)) {
        ERR_print_errors_fp(stdout);
        fprintf(stdout,"%s","Error Reading public Key");
        RSA_free(rsa);
        fclose(fp);
        return NULL;
        }
   	fclose(fp);
    if ((pk=EVP_PKEY_new()) == NULL){
        RSA_free(rsa);
        return NULL;
        }
 
	if (!EVP_PKEY_assign_RSA(pk,rsa)){
		EVP_PKEY_free(pk);
        RSA_free(rsa);
        fprintf(stdout,"%s","Error inserting key in EVP");
        return NULL;
        }
    return pk;
}

/* Test Verify Certificate
 * */
int verify_cert(X509*  x509){
    
    EVP_PKEY * pkey  = NULL;
    pkey = read_rsa_pubkey();
    if(!pkey) {
        fprintf(stdout,"%s","Error reading public_key");
        return 0;
    }
    
    if( X509_verify(x509, pkey) <=0 ) {
         ERR_print_errors_fp(stdout);
         fprintf(stdout,"%s","Error Verifying certificate");
         abort();
         return 0;
    }
    return 1;
}
